import json
from docx import Document
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
from docx.enum.style import WD_STYLE_TYPE
from docx.shared import Pt, RGBColor
from docx.oxml.ns import qn
from ConfigManager import ConfigManager
import Logger_config
import jsonschema
from jsonschema import validate
import re


class DocxStyleHandler:
    def __init__(self):
        """初始化日志配置和JSON Schema"""
        self.logger = Logger_config.setup_logger()
        self.cm = ConfigManager()        

    def create_or_update_styles(self, docx_document, style_config):
        """
        根据 JSON 配置为 Word 文档动态创建或更新样式并设置属性。

        :param docx_document: docx.Document 对象，目标文档
        :param style_config: dict，包含样式定义的 JSON 数据
        :return: dict，包含执行结果统计信息
        """

        try:
            if not isinstance(style_config.get("styles"), list):
                self.logger.error("styles 必须是数组类型")
                raise ValueError("styles 必须是数组类型")            
            
            word_style_schema = self.cm.get_merged_config()['word']['style']['schema']
            dict_word_style_schema = json.loads(word_style_schema)
            # 输入校验：使用 jsonschema 对传入的样式配置进行格式验证
            validate(instance=style_config, schema=dict_word_style_schema)
        except jsonschema.exceptions.ValidationError as ve:
            # 如果校验失败，记录错误日志并返回错误信息
            self.logger.error(f"JSON 格式验证失败: {ve.message}")
            return {
                "success": 0,
                "failed": 1,
                "failed_reason": f"JSON 校验失败: {ve.message}"
            }

        # 获取样式数组，若不存在则默认为空列表
        styles = style_config.get("styles", [])
        # 获取当前文档中已存在的所有样式名称集合
        existing_style_names = {style.name for style in docx_document.styles}

        success_count = 0  # 成功处理的样式数量计数器
        failed_count = 0   # 失败处理的样式数量计数器
        failed_styles = [] # 记录失败样式的名称和原因

        # 遍历每一个样式定义
        for style_def in styles:
            try:
                # 获取当前样式名称
                style_name = style_def.get("name")
                if not style_name:
                    raise ValueError("缺少样式名称")  # 若未指定 name 字段，则抛出异常

                # 解析样式类型（paragraph 或 character）
                style_type = self._resolve_style_type(style_def)

                # 判断样式是否已存在
                if style_name in existing_style_names:
                    new_style = docx_document.styles[style_name]
                    self.logger.info(f"样式 '{style_name}' 已存在，正在更新。")
                else:
                    new_style = docx_document.styles.add_style(style_name, style_type)
                    self.logger.info(f"样式 '{style_name}' 创建成功。")
                    # 更新缓存中的样式名称集合，供后续样式引用
                    existing_style_names.add(style_name)

                # 应用基础样式（basedOn）
                self._apply_base_style(new_style, style_def, docx_document, existing_style_names)

                # nextParagraphStyle 只适用于 paragraph 类型
                if style_type == WD_STYLE_TYPE.PARAGRAPH:
                    self._apply_next_paragraph_style(new_style, style_def, docx_document, existing_style_names)

                # 设置字体相关属性（统一）
                self._apply_font_settings(new_style, style_def)

                # 分支处理：根据样式类型应用不同设置
                if style_type == WD_STYLE_TYPE.PARAGRAPH:
                    self._apply_paragraph_settings(new_style, style_def)
                elif style_type == WD_STYLE_TYPE.CHARACTER:
                    self._apply_character_settings(new_style, style_def)

                # 设置语言属性（通用）
                self._apply_language_settings(new_style, style_def)

                success_count += 1  # 增加成功计数

            except Exception as e:
                # 捕获处理过程中发生的任何异常，记录日志并增加失败计数
                style_name = style_def.get("name", "未知样式")
                self.logger.error(f"处理样式 '{style_name}' 时发生错误: {e}")
                failed_count += 1
                failed_styles.append({
                    "name": style_name,
                    "error": str(e)
                })
                continue

        # 返回最终处理结果统计信息
        return {
            "success": success_count,
            "failed": failed_count,
            "failed_styles": failed_styles
        }

    def _resolve_style_type(self, style_def):
        """
        解析样式类型（paragraph 或 character）

        :param style_def: dict，单个样式的定义
        :return: WD_STYLE_TYPE 枚举值
        """
        style_type_str = style_def.get("styleType", "paragraph").lower()
        if style_type_str == "paragraph":
            return WD_STYLE_TYPE.PARAGRAPH
        elif style_type_str == "character":
            return WD_STYLE_TYPE.CHARACTER
        else:
            raise ValueError(f"不支持的样式类型: {style_type_str}")

    def _apply_base_style(self, new_style, style_def, doc, existing_names):
        """
        设置新样式的 base_style 属性（基于已有样式）

        :param new_style: 样式对象，要应用 base_style 的样式
        :param style_def: dict，样式定义数据
        :param doc: docx.Document 实例
        :param existing_names: 当前文档中存在的样式名称集合
        """
        based_on = style_def.get("basedOn")
        if based_on and based_on in existing_names:
            new_style.base_style = doc.styles[based_on]

    def _apply_next_paragraph_style(self, new_style, style_def, doc, existing_names):
        """
        设置新样式的 next_paragraph_style 属性（下一个段落样式）

        :param new_style: 样式对象，要应用 next_paragraph_style 的样式
        :param style_def: dict，样式定义数据
        :param doc: docx.Document 实例
        :param existing_names: 当前文档中存在的样式名称集合
        """
        next_style = style_def.get("nextParagraphStyle")
        if next_style and next_style in existing_names:
            new_style.next_paragraph_style = doc.styles[next_style]

    def _apply_font_settings(self, new_style, style_def):
        """
        应用字体相关设置到新样式上

        :param new_style: 样式对象，要应用字体设置的样式
        :param style_def: dict，样式定义数据
        """
        font_def = style_def.get("font", {})
        font = new_style.font

        if "latinFont" in font_def:
            font.name = font_def["latinFont"]  # 设置西文字体
        if "eastAsianFont" in font_def:
            # 设置中文字体，需通过 XML 操作
            rPr = new_style._element.get_or_add_rPr()
            rFonts = rPr.get_or_add_rFonts()
            rFonts.set(qn('w:eastAsia'), font_def["eastAsianFont"])

        if "size" in font_def:
            font.size = Pt(font_def["size"])  # 设置字号（以磅为单位）
        if "color" in font_def:
            color_hex = font_def["color"].lstrip("#")
            # 校验是否为合法的6位十六进制颜色
            if not re.fullmatch(r"[0-9a-fA-F]{6}", color_hex):
                color_hex = "000000"  # 设置为默认黑色
            r, g, b = int(color_hex[0:2], 16), int(color_hex[2:4], 16), int(color_hex[4:6], 16)
            font.color.rgb = RGBColor(r, g, b)

        # 设置常见的布尔型字体属性
        for attr in ["bold", "italic", "underline", "subscript", "superscript"]:
            if attr in font_def:
                setattr(font, attr, font_def[attr])

        # 设置数值型字体属性
        for attr in ["spacing", "scale", "kerning"]:
            if attr in font_def:
                setattr(font, attr, font_def[attr])

    def _apply_paragraph_settings(self, new_style, style_def):
        """
        应用段落相关设置到新样式上

        :param new_style: 样式对象，要应用段落设置的样式
        :param style_def: dict，样式定义数据
        """
        para_def = style_def.get("paragraph", {})
        paragraph_format = new_style.paragraph_format

        alignment_map = {
            "left": WD_PARAGRAPH_ALIGNMENT.LEFT,
            "center": WD_PARAGRAPH_ALIGNMENT.CENTER,
            "right": WD_PARAGRAPH_ALIGNMENT.RIGHT,
            "both": WD_PARAGRAPH_ALIGNMENT.JUSTIFY
        }

        # 设置段落对齐方式
        if "alignment" in para_def:
            alignment = para_def["alignment"]
            if alignment in alignment_map:
                paragraph_format.alignment = alignment_map[alignment]

        # 设置行距
        if "lineSpacing" in para_def:
            paragraph_format.line_spacing = para_def["lineSpacing"]

        # 显式映射缩进字段，避免下划线替换带来的命名错误
        indent_map = {
            "first_line_indent": "first_line_indent",
            "left_indent": "left_indent",
            "right_indent": "right_indent"
        }
        for key, attr in indent_map.items():
            if key in para_def:
                value = para_def[key]
                if isinstance(value, float):
                    value = round(value)           
                setattr(paragraph_format, attr, value)

        # 设置段前段后间距（以磅为单位）
        if "spaceBefore" in para_def:
            paragraph_format.space_before = Pt(para_def["spaceBefore"])
        if "spaceAfter" in para_def:
            paragraph_format.space_after = Pt(para_def["spaceAfter"])

        # 显式映射布尔型段落属性
        bool_attrs = {
            "keep_lines_together": "keep_lines_together",
            "keep_with_next": "keep_with_next",
            "page_break_before": "page_break_before",
            "widow_control": "widow_control",
            "contextual_spacing": "contextual_spacing",
            "auto_space_de": "auto_space_de",
            "auto_space_dn": "auto_space_dn"
        }
        for key, attr in bool_attrs.items():
            if key in para_def:
                setattr(paragraph_format, attr, para_def[key])
    
    def _apply_character_settings(self, new_style, style_def):
        """
        应用字符样式（character）的所有属性到 Word 样式对象中。

        :param new_style: 样式对象，要应用字符样式设置的样式
        :param style_def: dict，样式定义数据
        """
        if new_style.type != WD_STYLE_TYPE.CHARACTER:
            raise ValueError("不能将字符样式设置应用于非字符样式")

        # 处理 font 字段
        font_def = style_def.get("font", {})
        font = new_style.font

        if "latinFont" in font_def:
            font.name = font_def["latinFont"]  # 设置西文字体

        if "eastAsianFont" in font_def:
            # 设置中文字体（通过 XML 操作）
            rPr = new_style._element.get_or_add_rPr()
            rFonts = rPr.get_or_add_rFonts()
            rFonts.set(qn('w:eastAsia'), font_def["eastAsianFont"])

        if "size" in font_def:
            font.size = Pt(font_def["size"])  # 设置字号（以磅为单位）

        if "color" in font_def:
            color_hex = font_def["color"].lstrip("#")
            if not re.fullmatch(r"[0-9a-fA-F]{6}", color_hex):#  校验是否为合法的6位十六进制颜色
                color_hex = "000000"  # 设置为默认黑色
            r, g, b = int(color_hex[0:2], 16), int(color_hex[2:4], 16), int(color_hex[4:6], 16)
            font.color.rgb = RGBColor(r, g, b)

        for attr in ["bold", "italic", "underline", "subscript", "superscript"]:
            if attr in font_def:
                setattr(font, attr, font_def[attr])

        for attr in ["spacing", "scale", "kerning"]:
            if attr in font_def:
                setattr(font, attr, font_def[attr])

    def _apply_language_settings(self, new_style, style_def):
        """
        应用语言相关设置到新样式上

        :param new_style: 样式对象，要应用语言设置的样式
        :param style_def: dict，样式定义数据
        """
        lang_def = style_def.get("language", {})
        if lang_def and any(lang_def.values()):  # 只在非空且至少有一个值时设置
            rPr = new_style._element.get_or_add_rPr()
            langs = rPr.get_or_add_lang()

            if "eastAsia" in lang_def:
                langs.set(qn('w:eastAsia'), lang_def["eastAsia"])  # 设置东亚语言
            if "latin" in lang_def:
                langs.set(qn('w:val'), lang_def["latin"])          # 设置拉丁语系语言
            if "bidi" in lang_def:
                langs.set(qn('w:bidi'), lang_def["bidi"])          # 设置双向文本语言



    def apply_styles_and_save(self, output_path, document, styled_content):
        """
        根据传入的格式化JSON数据，将样式应用到Word文档的相应段落中，并保存文档。

        :param document: 一个 `Document` 对象，表示要修改的Word文档。
        :param styled_content: 包含段落内容和样式的JSON数据。格式如下：
            {
                "paragraphs": [
                    {"s": "h1", "t": "一级标题内容"},
                    {
                        "s": "p1",
                        "runs": [
                            {"t": "普通文本"},
                            {"t": "带样式的文本", "cs": "em1"}
                        ]
                    }
                ]
            }
        :return: 无，直接保存文档。
        """
        paragraphs_data = styled_content.get("paragraphs", [])
        
        for para_data in paragraphs_data:
            style_name = para_data.get("s")
            if not style_name:
                continue

            # 创建段落并应用段落样式
            paragraph = document.add_paragraph(style=style_name)

            # 先处理顶层的纯文本（t字段）
            top_level_text = para_data.get("t")
            if top_level_text:
                paragraph.add_run(top_level_text)  # 不指定字符样式

            # 再处理 runs 数组中的文本片段
            runs = para_data.get("runs", [])
            for run_data in runs:
                text = run_data.get("t")
                if not text:
                    continue

                char_style = run_data.get("cs", "").strip()
                if char_style:
                    paragraph.add_run(text=text, style=char_style)
                else:
                    paragraph.add_run(text=text)

        # 保存文档
        document.save(output_path)

if __name__ == "__main__":                     
    doc_handler = DocxStyleHandler()
